#!/usr/bin/python3
# Copyright by Kenneth Lee, 2023. All Right Reserved.
#
# A python script to general vi tags for sphinx project

import sys
import os
import re
import argparse

header = """\
!_TAG_FILE_FORMAT	2	/extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED	1	/0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR	Kenneth Lee	/Kenneth-Lee-2012@qq.com/
!_TAG_PROGRAM_NAME	rsttags	//
!_TAG_PROGRAM_VERSION	0.1 //
"""

matchers = [
        [re.compile(r':index:`(.+)`'), 'i'],        # index(role)
        [re.compile(r'\.\. index::\s*(\w+[a-z0-9 -_]+)'), 'i'],   # index(directive)
        [re.compile(r':doc:`(.+)`'), 'd'],          # doc
        [re.compile(r':dtag:`(.+)`'), 'D'],         # dtag with ``
        [re.compile(r'\.\. _`(.+)`:'), 'l'],        # link
        [re.compile(r'\.\. _([^`]+):'), 'l'],       # link
        [re.compile(r':ref:`.+<(.+)>`'), 'r'],      # ref with <>
        [re.compile(r':ref:`(.+)`'), 'r']           # ref without <>
]

all_types = 'dfilrsD'

class TitleChecker:
    def __init__(self):
        self.last_line = None;
        self.cur_title = None;

    def parse_line(self, line):
        "return current_title and a new found title"
        new_title = None
        is_sep = True
        if line and self.last_line and len(line) >= len(self.last_line):
            c = line[0]
            if c in '=-`:\'"~^_*+#<>':
                for i in line[1:]:
                    if c != i:
                        is_sep = False
                        break
            else:
                is_sep = False

            if is_sep:
                self.cur_title = self.last_line
                new_title = self.last_line

        self.last_line = line
        return self.cur_title, new_title

def get_tags(fn, types):
    tags=[] #tags record: name, filename, line, type, comment
    title = None
    new_title = None
    file_title = None
    title_checker = TitleChecker()

    if 'f' in types:
        tags.append([os.path.basename(fn), fn, None, 'f', fn])

    with open(fn, "r") as lines:
        for l in lines:
            if l[-1] == "\n":
                l = l[0:-1]

            if 's' in types:
                title, new_title = title_checker.parse_line(l)
                if not file_title:
                    file_title = title

                if new_title:
                        tags.append([new_title.strip(), fn, new_title, 's', None])
                        continue
                
            # I don't consider there are more than same tag in one line
            global matchers
            for m,t in matchers:
                if not t in types:
                    continue

                pat = m.search(l)
                if pat:
                    tag = pat.group(1).strip()
                    if t == 'd':
                        tags.append([os.path.basename(tag), fn, l, t, title])
                    else:
                        tags.append([tag, fn, l, t, title])

    return tags

def main():
    argvp = argparse.ArgumentParser(description="A Sphinx Tags Generator")
    argvp.add_argument('--type-before-name', '-t',
            nargs = 1,
            type = str,
            help = 'set a list of types to be added as prefix to tag name (valid types: ' + all_types + ')')
    argvp.add_argument('--types', '-T',
            nargs = 1,
            type = str,
            help = 'set a list of types to find (valid types: same as -t)')
    argvp.add_argument('dirs', nargs='*', type=str, help='source directory')
    args = argvp.parse_args()

    if args.dirs:
        tgts = args.dirs
    else:
        tgts = ['.']

    if args.types:
        types = args.types[0]
    else:
        types = all_types

    tags=[]
    for tgt in tgts:
        if os.path.isdir(tgt):
            for r,ds,fs in os.walk(tgt, topdown=True):
                for f in fs:
                    if f.endswith(".rst"):
                        tags.extend(get_tags(r + "/" + f, types))
        elif os.path.isfile(tgt):
            if tgt.endswith(".rst"):
                tags.extend(get_tags(tgt, types))

    if args.type_before_name:
        for r in tags:
            if args.type_before_name[0].find(r[3]) != -1:
                r[0] = r[3] + "_" + r[0]

    tags.sort()

    with open('tags', "w") as file:
        global header
        file.write(header)
        for r in tags:
            if r[2]:
                trans = {ord(r'/'): '\\/', ord('\\'): r'\\'}
                file.write(r[0] + '\t' + r[1] + '\t/^' + r[2].translate(trans) + '$/;"\t' + r[3])
            else:
                file.write(r[0] + '\t' + r[1] + '\t0;"\t' + r[3])

            if r[4]:
                file.write('\t' + r[4] + '\n')
            else:
                file.write('\n')

if __name__ == "__main__":
    main()

# vim: set ft=python
